//
//  PeersController.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/07.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "SolarSeekEngine.h"
#import "ServerConnectionController.h"
#import "PeersController.h"
#import "PeerConnectionsController.h"
#import "Peers.h"
#import "Shares.h"
#import "Connections.h"
//#import "SelfFillingMutables.h" // Pal: unused yet
#import "Message.h"

@class ControlledPeer;

@interface PeersController (Private)
- (void)dispatchMessageServer:(Message *)m;
- (void)addPrivilegesPeerFromMessage:(Message *)m;
- (void)updateInformationSharingPeerFromMessage:(Message *) m;
- (void)updateAvailabilityPeerFromMessage:(Message *)m;
- (void)addRemovablePeer:(Peer *)peer;
- (id)peerForDecodedPeer:(ControlledPeer *)peer;
- (void)sendTrackingMessageForPeer:(Peer *)peer;
@end


@interface ControlledPeer : Peer {
	PeersController *_peersController;
}

- (id)initWithController:(PeersController *)peersController name:(NSString *)name;
@end

@implementation ControlledPeer

- (id)initWithCoder:(NSCoder *)coder {
	self = [super initWithCoder:coder];
	_peersController = nil;
	return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
	[super encodeWithCoder:coder];
}

- (id)awakeAfterUsingCoder:(NSCoder *)coder {
	ControlledPeer *other = (ControlledPeer *)[[[NSApp engine] peersController] peerForDecodedPeer:self];
	if(other != self) {
		[other retain];
		[self release];
		return other;
	}
	else {
		_peersController = [[NSApp engine] peersController];
		return self;
	}
}

- (id)copyWithZone:(NSZone *)zone {
	return [self retain];
}

- (id)initWithController:(PeersController *)peersController name:(NSString *)aName {
	if(self = [super initWithName:aName]) {
		_peersController = peersController;
	}
	return self;
}

- (void)release {
	if([self retainCount]==2) {
		[super release];
		[_peersController addRemovablePeer:self];
	}
	else
		[super release];
}

@end

#pragma mark -

typedef enum { None = 0, ObserveServerMessage, ObserveLoggedIn } ObserveTypes;

@implementation PeersController

- (id)init { 
	[NSException raise:@"InvalidArgumentException" format:@"[PeersController init] is not a valid initializer."];
	[self release]; 
	return nil;
}

- (id)initWithEngine:(SolarSeekEngine *)engine {
	if(self = [super init]) {
		_peersPrivileged = [[NSMutableSet alloc] initWithCapacity:30000];
		//_peers = [[RandomMutableSet alloc] initWithCapacity:50000]; // Pal - I don't see any purpose of this
		_peers = [[NSMutableSet alloc] initWithCapacity:50000];
		_peersRemovable = [[NSMutableSet alloc] initWithCapacity:1000];
		_serverConnection = [[engine serverConnection] retain];
			[_serverConnection addObserver:self forKeyPath:@"messageReceived" options:nil context:(void *)ObserveServerMessage];
			[engine addObserver:self forKeyPath:@"serverConnectionController.loggedIn" options:nil context:(void *)ObserveLoggedIn];
		_peersTracking = [[NSMutableDictionary alloc] initWithCapacity:100];
	}
	return self;
}

- (void)dealloc {
	[[NSApp engine] removeObserver:self forKeyPath:@"serverConnectionController.loggedIn"];
	[_peersPrivileged release];
	[_peers release];
	[_peersRemovable release];
	[_serverConnection removeObserver:self forKeyPath:@"messageReceived"];
	[_serverConnection release];
	[_peersTracking release];
	[super dealloc];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	int type = (int)context;
	switch(type) {
		case ObserveServerMessage: {
			Message *m = [_serverConnection messageReceived];
			[self dispatchMessageServer:m];
			break;
		}
		case ObserveLoggedIn: {
			if([[[NSApp engine] serverConnectionController] loggedIn]) {
				NSEnumerator *e = [_peersTracking keyEnumerator];
				NSString *name;
				while(name = [e nextObject]) {
					[self sendTrackingMessageForPeer:[[NSApp engine] peerNamed:name]];
//					NSLog(@"Getting status of %@.",name);
				}
			}
			break;
		}
		default:
			[NSException raise:@"UnknownMessageObservedException" format:@"PeersController observed unknown key path %@ from object %@.",keyPath,object];
	}
}

#pragma mark key retrieval

- (NSString *)description {
	NSEnumerator *e = [[self peersAll] objectEnumerator];
	Peer *p;
	NSMutableString *s = [NSMutableString stringWithString:@"Dumping all known peers...\n"];
	
	while(p = [e nextObject]) {
		if([[p valueForKey:@"privileged"] intValue])
			[s appendFormat:@"    %@ has PRIVILEGES.\n",p];
		else
			[s appendFormat:@"    %@ does not have privileges.\n",p];
	}
	[s appendFormat:@"%d peers.\n",[_peers count]];
	[s appendFormat:@"%d privileged peers.",[_peersPrivileged count]];
	return s;
}
		
- (id)peersByName { return _peers; }
- (NSArray *)peersPrivileged { return [_peersPrivileged allObjects]; }
- (NSArray *)peersAll { return [_peers allObjects]; }

- (Peer *)peerForName:(NSString *)name {
	Peer *p = [[[Peer alloc] initWithName:name] autorelease];
	p = [_peers member:p];
	if(p==nil) {
		p = [[[ControlledPeer alloc] initWithController:self name:name] autorelease];
		[_peers addObject:p];
	}
	return p;
}

- (id)peerForDecodedPeer:(ControlledPeer *)controlledPeer {
	Peer *p = [_peers member:controlledPeer];
	if(p==nil) {
		[_peers addObject:controlledPeer];
		p = controlledPeer;

	}
	else if(p!=controlledPeer) {
//		id shares = [[controlledPeer sharesTreeRoot] sharesThisAndSubLevels];
//		NSEnumerator *e = [shares objectEnumerator]
//		[p addShares:shares];
	}
	return p;
}

- (void)addRemovablePeer:(Peer *)peer {
	[_peers removeObject:peer];
}


#pragma mark peer altering methods.

- (void)dispatchMessageServer:(Message *)m {
	[m rewind];
	int code = [m getCode];
	NS_DURING
		switch(code) {
			case TrackUser: {
				NSString *name = [m getString];
				BOOL exists = [m getByte]!=0;
				[[self peerForName:name] setExists:exists];
				break;
			}
			case PrivilegedUsers:
//				NSLog(@"SM: Received a privileged users message...");
				[self addPrivilegesPeerFromMessage:m];
				break;
			case GetUserStatus:
//				NSLog(@"SM: Received a user status message.");
				[self updateAvailabilityPeerFromMessage:m];
				break;
			case JoinRoom:
			case GetUserStats:
//				NSLog(@"SM: Received a user stats message.");
				[self updateInformationSharingPeerFromMessage:m];
				break;
			case GetPeerAddress: {
				NSString *name = [m getString];
				int addr = [m getInt];
				int port = [m getInt];
				if(addr==0 || port==0)
					[[self peerForName:name] setAvailability:AvailabilityOffline];
			}
		}
	NS_HANDLER
		NSLog(@"ERROR: Could not decode %d byte SERVER message with code %d: %@",[[m data] length],code,localException);
		NSLog(@"ERROR: Ignoring exception and continuing...");
	NS_ENDHANDLER
}

- (void)addPrivilegesPeerFromMessage:(Message *)m {
	//fejta: TODO: make this threaded.
	BOOL will= NO;
	NS_DURING
		unsigned count, count2 = [m getInt];
		if(count2) {
			[self willChangeValueForKey:@"peersPriviliged"];
			will = YES;
		}
		for(count=count2;count>0;count--) {
			NSString *name = [m getString];
			Peer *p = [self peerForName:name];
			[p addPrivileges:PeerPrivilegesDonated];
			[_peersPrivileged addObject:p];
		}
		if(count2) {
			[self didChangeValueForKey:@"peersPriviliged"];
			will = NO;
		}
//		NSLog(@"PC: %d peers privileged (%d new).",[_peersPrivileged count],count2);
	NS_HANDLER
//		NSLog(@"PC: Received a %d byte privileged peer message that is possibly compressed.  Attempting decompression.",[[m data] length]);
		[m rewind];
		[m getCode];
		if([m decompress]) {
			unsigned dcount, dcount2 = [m getInt];
			if(dcount2 && will==NO) {
				will = YES;
				[self willChangeValueForKey:@"peersPrivileged"];
			}
			for(dcount=dcount2;dcount>0;dcount--) {
				NSString *name = [m getString];
				Peer *p = [self peerForName:name];
				[p addPrivileges:PeerPrivilegesDonated];
				[_peersPrivileged addObject:p];
			}
			if(dcount2) {
				[self didChangeValueForKey:@"peersPriviliged"];
				will = NO;
			}
//			NSLog(@"PC (compressed): %d peers privileged (%d new).",[_peersPrivileged count],dcount2);
		}
//		else 
//			NSLog(@"ERROR: Could not decode privileged peer message: %@",localException);
	NS_ENDHANDLER
}

- (void)sendTrackingMessageForPeer:(Peer *)peer {
	Message *m = [Message messageWithCode:TrackUser];
	[m putString:[peer name]];
	[[NSApp engine] sendServerMessage:m];
	m = [Message messageWithCode:GetUserStatus];
	[m putString:[peer name]];
	[[NSApp engine] sendServerMessage:m];
}


- (void)trackAvailabilityForPeer:(Peer *)peer {
	NSNumber *n = [_peersTracking objectForKey:[peer name]];
	if(n==nil) {
		if([[[NSApp engine] serverConnectionController] loggedIn])
			[self sendTrackingMessageForPeer:peer];
		n = [NSNumber numberWithUnsignedInt:1];
		[_peersTracking setObject:n forKey:[peer name]];
	}
	else {
		[_peersTracking setObject:[NSNumber numberWithUnsignedInt:[n unsignedIntValue]+1] forKey:[peer name]];
	}
}

- (void)refreshInformationForPeer:(Peer *)peer {
	if(peer==nil) return;
	Message *m = [Message messageWithCode:UserInfoRequest];
	PeerConnection *pc = [[[NSApp engine] peerConnectionsController] peerConnectionForPeer:peer];
	[pc sendMessage:m];
}


- (void)refreshInformationForPeers:(id)enumeratableObject {
	NSEnumerator *e = [enumeratableObject objectEnumerator];
	Peer *p;
	while(p = [e nextObject])
		[self refreshInformationForPeer:p];
}

- (void)stopTrackingAvailabilityForPeer:(Peer *)peer {
	NSNumber *n = [_peersTracking objectForKey:[peer name]];
	if(n) {
		unsigned num = [n unsignedIntValue]-1;
		if(num==0)
			[_peersTracking removeObjectForKey:[peer name]];
		else
			[_peersTracking setObject:[NSNumber numberWithUnsignedInt:num] forKey:[peer name]];
	}
}

- (void)updateAvailabilityPeerFromMessage:(Message *)m {
	NSString *name = [m getString];
	Peer *p = [self peerForName:name];
	Availability a = [m getInt];
	[p setAvailability:a];
}

- (void)updateInformationSharingPeerForPeer:(Peer *)p fromMessage:(Message *)m {
	unsigned avgspd = [m getInt];
	unsigned downloadnum = [m getInt];
	unsigned something = [m getInt];
	unsigned files = [m getInt];
	unsigned dirs = [m getInt];
	[p setValue:[NSNumber numberWithUnsignedInt:avgspd] forKey:@"speedAverage"];
	[p setValue:[NSNumber numberWithUnsignedInt:files] forKey:@"countFiles"];
	[p setValue:[NSNumber numberWithUnsignedInt:dirs] forKey:@"countFolders"];
}

- (void)updateInformationSharingPeerFromMessage:(Message *) m {
	[m rewind];
	int code = [m getCode];
	switch(code) {
	case GetUserStats:
		{
			NSString *name = [m getString];
			Peer *p = [self peerForName:name];
			[self updateInformationSharingPeerForPeer:p fromMessage:m];
		}
		break;
	case JoinRoom:
		{
			NSString *name = [m getString];
			unsigned n,size = [m getInt];
			NSMutableArray *a = [NSMutableArray arrayWithCapacity:size];
			for(n=size;n>0;n--) {
				NSString *name = [m getString];
				[a addObject:[self peerForName:name]];
			}
			n = [m getInt];
			NSAssert2(n==size,@"PeersController received malformed JoinRoom message.  Number of peers: %i does not match number of peer statuses: %i",size,n);
			NSEnumerator *e = [a objectEnumerator];
			Peer *p;
			while(p = [e nextObject]) {
				n = [m getInt];
				[p setAvailability:n];
			}
			n = [m getInt];
			NSAssert2(n==size,@"PeersController received malformed JoinRoom message.  Number of peers: %i does not match number of peer statistics: %i",size,n);
			e = [a objectEnumerator];
			while(p = [e nextObject])
				[self updateInformationSharingPeerForPeer:p fromMessage:m];
			//fejta: TODO: "slotsfull data being ignored."
		}
		break;
	}
}
@end
